package top.codedance.iotp.client.plugins.sync.opc;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.ClientApplication;
import top.codedance.iotp.client.net.listenner.PluginService;
import top.codedance.iotp.common.entity.PluginResultEntity;
import org.apache.log4j.Logger;
import org.ini4j.Ini;
import org.ini4j.Wini;
import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.common.JISystem;
import org.jinterop.dcom.core.JIUnsignedInteger;
import org.jinterop.dcom.core.JIUnsignedShort;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.*;
import org.openscada.opc.lib.da.browser.Branch;
import org.openscada.opc.lib.da.browser.Leaf;
import org.openscada.opc.lib.da.browser.TreeBrowser;
import org.openscada.opc.lib.list.ServerList;

import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Executors;

public class OpcDaService implements PluginService, Runnable {
    private Logger logger = ClientApplication.getLogger(OpcDaService.class);

    private static final String SERVICE_NAME = "OpcDaService";
    private Integer clientId = 0;
    private boolean status = false;
    private Wini ini = null;

    private OpcDaService(Integer clientId, Wini ini) {
        this.clientId = clientId;
        this.ini = ini;
    }

    public static PluginService getInstance(Integer clientId, Wini ini) {
        PluginService pluginService = OpcExecuterImpl.getPluginService(SERVICE_NAME + clientId);
        if (null == pluginService) {
            OpcDaService opcDaService = new OpcDaService(clientId, ini);
            OpcExecuterImpl.pluginServiceWorkerPool.submit(opcDaService);
            pluginService = OpcExecuterImpl.registPluginService(SERVICE_NAME + clientId, opcDaService);
        }
        return pluginService;
    }

    @Override
    public void run() {
        try {
            init();
        } catch (Exception e) {
            logger.debug(e.getMessage());
            OpcExecuterImpl.removePluginService(SERVICE_NAME + clientId);
            System.out.println("OPC DA client init fail.");
        }
    }

    private String host = "localhost";
    private String domain = "";
    private String progId = "SWToolbox.TOPServer.V5";
    private String user = "codedance";
    private String password = "123456";

    private boolean onoff = true;
    private Server s;
    private int reInitCount = 0;

    @Override
    public void init() throws Exception {
        try {
            if (ini != null && ini.containsKey("opc")) {
                Ini.Section section = ini.get("opc");
                if (section.containsKey("host." + this.clientId)) {
                    host = section.get("host." + this.clientId);
                } else {
                    throw new Exception("parameter host is empty.");
                }
                if (section.containsKey("domain." + this.clientId)) {
                    domain = section.get("domain." + this.clientId);
                } else {
                    throw new Exception("parameter domain is empty.");
                }
                if (section.containsKey("progId." + this.clientId)) {
                    progId = section.get("progId." + this.clientId);
                } else {
                    throw new Exception("parameter progId is empty.");
                }
                if (section.containsKey("user." + this.clientId)) {
                    user = section.get("user." + this.clientId);
                } else {
                    throw new Exception("parameter user is empty.");
                }
                if (section.containsKey("password." + this.clientId)) {
                    password = section.get("password." + this.clientId);
                } else {
                    throw new Exception("parameter password is empty.");
                }
            } else {
                throw new Exception("opc_da_service parameters is empty.");
            }
        } catch (ClassNotFoundException | SQLException e) {
            throw new Exception("opc_da_service init parameters fail.");
        }

        AutoReconnectController autos = null;
        try {
            JISystem.setAutoRegisteration(true);
            ConnectionInformation connectionInfo = new ConnectionInformation();
            connectionInfo.setHost(host);
            connectionInfo.setUser(user);
            connectionInfo.setDomain(domain);
            connectionInfo.setPassword(password);
            ServerList serverList = new ServerList(host, user, password, domain);
            String clsId = serverList.getClsIdFromProgId(progId);
            connectionInfo.setClsid(clsId);
            s = new Server(connectionInfo, Executors.newSingleThreadScheduledExecutor());
            s.addStateListener(status -> OpcDaService.this.status = status);
            autos = new AutoReconnectController(s, 1000 * 60 * 60);
            autos.connect();
            while (onoff) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            logger.debug(e.getMessage());
            throw new Exception("opc_da_service connect opc server fail.");
        } finally {
            if (autos != null) {
                autos.disconnect();
            }
            Thread.sleep(2000);
            onoff = true;
            System.out.println("opc client reinit...");
            if (reInitCount < 2) {
                reInitCount++;
                init();
            }
        }
    }

    private void waiting() {
        long time = System.currentTimeMillis();
        while ((s == null || !status) && time - System.currentTimeMillis() <= 5000) {
            try {
                Thread.sleep(100);
            } catch (Exception ex) {
            }
            time = System.currentTimeMillis();
        }
    }

    @Override
    public PluginResultEntity execute(JSONObject jo) {
        waiting();
        PluginResultEntity endResult = new PluginResultEntity();
        JSONObject outResult = new JSONObject();
        try {
            TreeBrowser treeBrowser = s.getTreeBrowser();
            Branch browse = treeBrowser.browse();
            brower(browse, outResult, "");
        } catch (Exception e) {
            logger.debug(e.getMessage());
        }
        endResult.setContext(outResult);
        endResult.setOk(true);
        return endResult;
    }

    /**
     * 递归遍历所有节点
     * @param branch
     * @param ja
     * @param path
     */
    private void brower(Branch branch, JSONObject ja, String path) {
        if (branch.getParent() != null) {
            path += path.isEmpty() ? branch.getName() : "." + branch.getName();
        }
        Collection<Leaf> leaves = branch.getLeaves();
        if (leaves.size() > 0) {
            for (Leaf leaf : leaves) {
                ja.put(path + "." + leaf.getName(), new JSONObject());
            }
        }
        Collection<Branch> branches = branch.getBranches();
        if (branches.size() > 0) {
            for (Branch b : branches) {
                JSONObject sja = new JSONObject();
                brower(b, branch.getParent() == null ? ja : sja, path);
                ja.put(b.getName(), sja);
            }
        }
    }

    @Override
    public PluginResultEntity execute(JSONArray inJsonArray) {
        waiting();
        PluginResultEntity endResult = new PluginResultEntity();
        JSONArray outResult = new JSONArray();
        if (s != null) {
            List<JSONObject> readArray = new ArrayList<>();
            List<JSONObject> writeArray = new ArrayList<>();
            for (int i = 0; i < inJsonArray.size(); i++) {
                String tag = null;
                boolean isWrite = false;
                Object v = null;
                JSONObject jo = null;
                try {
                    try {
                        jo = inJsonArray.getJSONObject(i);
                    } catch (Exception e) {
                    }
                    if (jo == null) {
                        tag = inJsonArray.getString(i);
                    } else {
                        tag = jo.getString("tag");
                        if (jo.containsKey("isWrite")) {
                            isWrite = jo.getBoolean("isWrite");
                            if (isWrite) {
                                v = jo.get("value");
                            }
                        }
                    }
                } catch (Exception ex) {
                    logger.debug(ex.getMessage());
                }
                Object ext = null;
                try {
                    ext = jo.get("ext");
                } catch (Exception ex) {
                }

                if (isWrite) {
                    JSONObject tmpJo = new JSONObject();
                    tmpJo.put("tag", tag);
                    tmpJo.put("ext", ext);
                    tmpJo.put("value", v);
                    writeArray.add(tmpJo);
                } else {
                    JSONObject tmpJo = new JSONObject();
                    tmpJo.put("tag", tag);
                    tmpJo.put("ext", ext);
                    readArray.add(tmpJo);
                }
            }
            if (readArray.size() > 0) {
                reads(readArray, outResult);
            }
            if (writeArray.size() > 0) {
                writes(writeArray, outResult);
            }
            endResult.setOk(true);
        } else {
            for (int i = 0; i < inJsonArray.size(); i++) {
                try {
                    String tag;
                    JSONObject jo = null;
                    try {
                        jo = inJsonArray.getJSONObject(i);
                    } catch (Exception e) {
                    }
                    if (jo == null) {
                        tag = inJsonArray.getString(i);
                    } else {
                        tag = jo.getString("tag");
                    }
                    Object ext = null;
                    try {
                        ext = jo.get("ext");
                    } catch (Exception ex) {
                    }
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("id", tag);
                    jsonObject.put("type", null);
                    jsonObject.put("value", null);
                    jsonObject.put("remarks", "OpcDa client no connection created.");
                    jsonObject.put("ext", ext);
                    outResult.add(jsonObject);
                } catch (Exception ex) {
                }
            }
            endResult.setOk(false);
            OpcExecuterImpl.removePluginService(SERVICE_NAME + clientId);
        }
        endResult.setContext(outResult);
        return endResult;
    }

    /**
     * 批量读取
     *
     * @param readArray
     * @param outResult
     */
    private void reads(List<JSONObject> readArray, JSONArray outResult) {
        try {
            Group group = s.addGroup();
            List<String> tags = new ArrayList<>();
            Map<String, Object> exts = new HashMap<>();
            List<Item> items = new ArrayList<>();
            for (JSONObject jo : readArray) {
                tags.add(jo.getString("tag"));
                exts.put(jo.getString("tag"), jo.get("ext"));
                items.add(group.addItem(jo.getString("tag")));
            }
            Item[] itemArr = items.toArray(new Item[]{});
            Map<Item, ItemState> resultMap = group.read(true, itemArr);
            for (Item key : resultMap.keySet()) {
                ItemState is = resultMap.get(key);
                outResult.add(dumpValue(key.getId(), is.getValue(), exts.get(key.getId())));
            }
        } catch (Exception ex) {
            for (JSONObject jo : readArray) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("id", jo.getString("tag"));
                jsonObject.put("type", null);
                jsonObject.put("value", null);
                jsonObject.put("ext", jo.get("ext"));
                jsonObject.put("good", false);
                jsonObject.put("remarks", ex.getMessage());
                outResult.add(jsonObject);
            }
        }
    }

    /**
     * 批量写入
     *
     * @param writeArray
     * @param outResult
     */
    private void writes(List<JSONObject> writeArray, JSONArray outResult) {
        try {
            Group group = s.addGroup();
            List<String> tags = new ArrayList<>();
            Map<String, Object> exts = new HashMap<>();
            Map<String, Object> values = new HashMap<>();
            List<WriteRequest> requests = new ArrayList<>();
            for (JSONObject jo : writeArray) {
                tags.add(jo.getString("tag"));
                exts.put(jo.getString("tag"), jo.get("ext"));
                values.put(jo.getString("tag"), jo.get("value"));
                Item item = group.addItem(jo.getString("tag"));
                JIVariant value = getValue(jo.get("value"));
                requests.add(new WriteRequest(item, value));
            }
            WriteRequest[] writeRequestArr = requests.toArray(new WriteRequest[]{});
            Map<Item, Integer> resultMap = group.write(writeRequestArr);
            for (Item key : resultMap.keySet()) {
                Integer integer = resultMap.get(key);
                JSONObject jo = new JSONObject();
                jo.put("tag", key.getId());
                jo.put("type", values.get(key.getId()).getClass().getTypeName());
                jo.put("value", values.get(key.getId()));
                jo.put("good", integer != null);
                jo.put("ext", exts.get(key.getId()));
                outResult.add(jo);
            }
        } catch (Exception ex) {
            for (JSONObject jo : writeArray) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("id", jo.getString("tag"));
                jsonObject.put("type", jo.get("value").getClass().getTypeName());
                jsonObject.put("value", jo.getString("value"));
                jo.put("good", false);
                jsonObject.put("ext", jo.getString("ext"));
                outResult.add(jo);
            }
        }
    }

    /**
     * 讯取单个
     *
     * @param item
     * @param outResult
     * @param ext
     * @throws JIException
     */
    private void read(Item item, JSONArray outResult, Object ext) throws JIException {
        JIVariant value = item.read(true).getValue();//true 2即使消息;false 1缓存消息
        outResult.add(dumpValue(item.getId(), value, ext));
    }

    /**
     * 写入单个
     *
     * @param item
     * @param v
     * @param outResult
     * @param ext
     * @throws JIException
     */
    private void write(Item item, Object v, JSONArray outResult, Object ext) throws JIException {
        JIVariant value = getValue(v);
        try {
            item.write(value);
            outResult.add(dumpValue(item.getId(), value, ext));
        } catch (JIException ex) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("id", item.getId());
            jsonObject.put("type", v.getClass().getName());
            jsonObject.put("value", null);
            jsonObject.put("remarks", ex.getMessage());
            jsonObject.put("ext", ext);
            outResult.add(jsonObject);
            if (ex.getMessage().equals("Connect server fail.")) {
                restart();
            }
        }
    }

    /**
     * 获取值
     *
     * @param v
     * @return
     */
    private JIVariant getValue(Object v) {
        JIVariant value;
        if (v instanceof Boolean) {
            value = new JIVariant((boolean) v);
        } else if (v instanceof Long) {
            value = new JIVariant((long) v);
        } else if (v instanceof Integer) {
            value = new JIVariant((int) v);
        } else if (v instanceof Short) {
            value = new JIVariant((short) v);
        } else if (v instanceof Byte) {
            value = new JIVariant((byte) v);
        } else if (v instanceof Double) {
            value = new JIVariant((double) v);
        } else if (v instanceof Float) {
            value = new JIVariant((float) v);
        } else {
            value = new JIVariant(v.toString());
        }
        return value;
    }

    /**
     * 数据类型解析
     *
     * @param id 标签id
     * @param v  结果
     * @return
     * @throws JIException
     */
    private JSONObject dumpValue(String id, JIVariant v, Object ext) throws JIException {
        Object value;
        if (!v.isArray()) {
            value = v.getObject();
        } else {
            value = v.getObjectAsArray().toString();
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("id", id);
        if (value instanceof Boolean) {
            jsonObject.put("type", Boolean.class.getName());
            jsonObject.put("value", Boolean.valueOf(value.toString()));
        } else if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) {
            jsonObject.put("type", Long.class.getName());
            jsonObject.put("value", Long.valueOf(value.toString()));
        } else if (value instanceof Double || value instanceof Float) {
            jsonObject.put("type", Double.class.getName());
            jsonObject.put("value", Double.valueOf(value.toString()));
        } else if (value instanceof JIUnsignedShort) {
            JIUnsignedShort vl = (JIUnsignedShort) value;
            jsonObject.put("type", Long.class.getName());
            jsonObject.put("value", Long.valueOf(vl.getValue().toString()));
        } else if (value instanceof JIUnsignedInteger) {
            JIUnsignedInteger vi = (JIUnsignedInteger) value;
            jsonObject.put("type", Integer.class.getName());
            jsonObject.put("value", Integer.valueOf(vi.getValue().toString()));
        } else if (value instanceof Date) {
            jsonObject.put("type", Date.class.getName());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            jsonObject.put("value", sdf.format(value));
        } else {
            try {
                String str = v.getObjectAsString2();
                jsonObject.put("type", String.class.getName());
                jsonObject.put("value", str.isEmpty() ? null : str);
            } catch (Exception e) {
                jsonObject.put("type", Object.class.getName());
                jsonObject.put("value", v.getObject());
            }
        }
        jsonObject.put("good", true);
        jsonObject.put("ext", ext);
        return jsonObject;
    }

    /**
     * 重新连接
     */
    private void restart() {
        onoff = false;
        OpcExecuterImpl.removePluginService(SERVICE_NAME + clientId);
    }
}
